package com.zte.iptvclient.android.androidsdk;
import com.huawei.authapk.SLog;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.http.*;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.HttpParams;
import org.apache.http.util.EntityUtils;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIUtils;
import org.apache.http.entity.StringEntity;


//登录相关接口
//(class SDKLoginMgr)
//主要实现向平台登录、保活、注销等功能，相关接口由
//class com.zte.iptvclient.android.androidsdk.SDKLoginMgr
//类提供。

public class SDKLoginMgr {
    private static SDKLoginMgr m_instance = null;
    private SDKLoginMgr() {
        SLog.d("SDKLoginMgr()");
    }   

    public static final int LOGINMGR_SUCCESS = 0;		// 成功
    public static final int LOGINMGR_AUTHFAIL = 1;		// 认证失	败（帐号密码错误）
    public static final int LOGINMGR_TIMEDOUT = 2;		// EPG超时
    public static final int LOGINMGR_HTTPFAIL = 3;		// HTTP请求失败（某些场景下等同于认证失败）
    public static final int LOGINMGR_PARSEFAIL = 4;		// 解析内容失败（可能是EPG发生了变化）
    
    
    public interface ILoginReturnListener
    {   
        // 功能描述:    登录请求startLogin所对应的响应回调函数
        // 参数说明:    strErrorCode: 错误码，根据此错误码判断登录成功或失败。
        //              strErrorMsg: 错误描述信息
        public void onLoginReturn(String strErrorCode, String strErrorMsg);
        public void onLogoutReturn(String strErrorCode, String strErrorMsg);
    }   

    private ILoginReturnListener m_listener = null;


    private final String eds = "http://223.87.5.102:8082/EDS/jsp/AuthenticationURL";
    private String strClientType = null;
    private String strIpAddr = null;
    private String strMacAddr = null;
    private String strAndroidID = null;
    private String strUserID = null;
    private String strPassword = null;
    private boolean isHttpGet = true;
    private String strEncryToken = null;
    private String strUserToken = null;
    private int m_status = LOGINMGR_AUTHFAIL;
    
    // private HttpRequestBase httpRequest = null;
    
    private String ConcatenateUrl(boolean login)
    {
    	StringBuilder builder = new StringBuilder(eds);
    	
    	builder.append("?UserID=").append(strUserID);
    	builder.append("&Action=");
    	if (login) {
    		builder.append("Login");
    	} else {
    		builder.append("Logout");
    	}
    	return builder.toString();
    }
    
    private String CheckAndConcatenateNonAbsoluteUrl(String baseUrl, String url)
    {
    	URI uri = URI.create(url);
    	if (!uri.isAbsolute()) {
    		int pos = baseUrl.lastIndexOf("/");
    		if (pos > 0) {
    			url = baseUrl.substring(0, pos) + "/" + url;
    		} else {
    			url = baseUrl + "/" + url;
    		}
    	}
		try {
			uri = URIUtils.resolve(new URI(baseUrl), url);
		} catch (URISyntaxException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		url = uri.toString();
    	return url;
    }
    
    private String Md5Encrypt163(String s)
    {
        char hexDigits[]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};       
        try {
            byte[] btInput = s.getBytes();
            // 获得MD5摘要算法的 MessageDigest 对象
            MessageDigest mdInst = MessageDigest.getInstance("MD5");
            // 使用指定的字节更新摘要
            mdInst.update(btInput);
            // 获得密文
            byte[] md = mdInst.digest();
            
            // 把密文转换成十六进制的字符串形式
            int j = md.length;
            char str[] = new char[j * 2];
            int k = 0;
            for (int i = 0; i < j; i++) {
                byte byte0 = md[i];
                str[k++] = hexDigits[byte0 >>> 4 & 0xf];
                str[k++] = hexDigits[byte0 & 0xf];
            }
            return new String(str);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    
    private String Md5EncryptHuawei(String s)
    {
        char hexDigits[]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};       
        try {
            byte[] btInput = s.getBytes();
            // 获得MD5摘要算法的 MessageDigest 对象
            MessageDigest mdInst = MessageDigest.getInstance("MD5");
            // 使用指定的字节更新摘要
            mdInst.update(btInput);
            // 获得密文
            byte[] md = mdInst.digest();
            
            // 把密文转换成十六进制的字符串形式
            int j = md.length;
            char str[] = new char[j * 2];
            int k = 0;
            for (int i = 0; i < j; i++) {
                byte byte0 = md[i];
                if ((byte0 >>> 4 & 0xf) != 0) {
                	str[k++] = hexDigits[byte0 >>> 4 & 0xf];
                }
                str[k++] = hexDigits[byte0 & 0xf];
            }
            return new String(str);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    
    private String byte2hex(byte[] b) {
        String hs="";
        String stmp="";

        for (int n=0; n<b.length; n++) {
            stmp = (java.lang.Integer.toHexString(b[n] & 0XFF));
            if (stmp.length() == 1)
            	hs = hs + "0" + stmp;
            else 
            	hs = hs + stmp;
        }
        return hs.toUpperCase();
    }
    
    private String ThreeDes(String s, String key)
    {
    	try {
    		// SecretKey  desKey = new SecretKeySpec(key.getBytes("UTF-8"), "DES");
    		
    		DESKeySpec desKeySpec = new DESKeySpec(key.getBytes("UTF-8"));
    		SecretKey desKey = SecretKeyFactory.getInstance("DES").generateSecret(desKeySpec);
    		
    		Cipher c1 = Cipher.getInstance("DES/ECB/PKCS5Padding");
    		c1.init(Cipher.ENCRYPT_MODE, desKey);    		

    		return byte2hex(c1.doFinal(s.getBytes("UTF-8")));
    	} catch (Exception e) {
    		e.printStackTrace();
    	}
    	return null;
    }
  
    
    private String ParseCTCSetConfig(String content, String key)
    {
		// 取加密方式
		Pattern	pattern = Pattern.compile("Authentication.CTCSetConfig\\s*\\(['\"]" + key + "['\"],\\s*['\"].*['\"]\\);");
		Matcher mat = pattern.matcher(content);
		if (mat.find()) {
			String sr = mat.toMatchResult().group();
			pattern = Pattern.compile(",\\s*['\"].*['\"]");
			mat = pattern.matcher(sr);
			if (mat.find()) {
				sr = mat.toMatchResult().group();
				String r = sr.substring(1).trim();
				r = r.substring(1, r.length() - 1);
				return r;
			}
		}
		return null;
    }
    
    private String CTCGetAuthInfo(String EncryptionToken, String EncryptionType)
    {
    	String password = null;
    	
    	if (EncryptionType.equals("0001")) {
    		password = this.strPassword;
    	} else if (EncryptionType.equals("0002")) {
    		password = Md5EncryptHuawei(this.strPassword);
    		password = password.substring(0, 8);
    	} else if (EncryptionType.equals("0003")) {
    		password = Md5Encrypt163(this.strPassword);
    		password = password.substring(0, 24);
    	} else {
    		SLog.d("unknown EncryptionType.");
    		password = this.strPassword;
    	}
    	while (password.length() < 24) {
    		password += "0";
    	}
    	
    	SLog.d("password = " + password);
    	
    	Random rand = new Random();
    	String randstr = String.valueOf(rand.nextInt(99999999));
    	
    	String source = randstr 
    			+ "$" + EncryptionToken 
    			+ "$" + this.strUserID 
    			+ "$" + this.strAndroidID 
    			+ "$" + this.strIpAddr 
    			+ "$" + this.strMacAddr 
    			+ "$Reserved$CTC";
    	
    	String authinfo = ThreeDes(source, password);
    	
    	SLog.d("authinfo = " + authinfo);
    	// return "12345678901234567890123456789012";
    	return authinfo;
    }
    
    private String ParseAndGetNextUrl(HttpResponse response, String currentUrl)
    {
    	HttpEntity entity = response.getEntity();
		SLog.d("--------------------");
		SLog.d("" + response.getStatusLine());
		
		String content = "";
		if (entity != null) {
			try {
				content = EntityUtils.toString(entity);
			} catch (ParseException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		SLog.d(content);
		SLog.d(currentUrl);
		
		final String AUTHLOGINHWCTC = "<form action=\"authLoginHWCTC.jsp?UserID=";
		final String VALIDAUTHENTICAIONHWCTC = "<form action=\"ValidAuthenticationHWCTC.jsp\" name=\"authform\" method=\"post\">";

		int pos = -1;
		if (currentUrl.indexOf("AuthenticationURL") >= 0 &&
			(pos = content.indexOf(AUTHLOGINHWCTC)) >= 0)	
		{
			String f = content.substring(pos + ("<form action=\"").length());
			pos = f.indexOf("\"");
			if (pos >= 0)
				f = f.substring(0, pos);
			
			String url = CheckAndConcatenateNonAbsoluteUrl(currentUrl, f);
			url += "&isfirst=0";
			
			isHttpGet = false;
			SLog.d("url = " + url); 
			return url;
		} else if (currentUrl.indexOf("authLoginHWCTC.jsp") > 0 &&
			(pos = content.indexOf(VALIDAUTHENTICAIONHWCTC)) > 0)
		{
			String f = content.substring(pos + ("<form action=\"").length());
			pos = f.indexOf("\"");
			if (pos >= 0)
				f = f.substring(0, pos);
			String url = CheckAndConcatenateNonAbsoluteUrl(currentUrl, f);
			
			// 取加密方式
			String EncryptionType = ParseCTCSetConfig(content, "EncryptionType");
			SLog.d("EncryptionType = " + EncryptionType);
			
			// 取EncryptToken
			Pattern pattern = Pattern.compile("var\\s{1,}EncryptToken\\s*=\\s*['\"][0-9a-zA-Z]*['\"];");
			Matcher mat = pattern.matcher(content);
			String EncryptToken = "";
			if (!mat.find()) {
				m_status = LOGINMGR_PARSEFAIL;
		        return null;
			} else {
				String sr = mat.toMatchResult().group();
				pattern = Pattern.compile("['\"][0-9a-zA-Z]*['\"]");
				mat = pattern.matcher(sr);
				if (mat.find()) {
					EncryptToken = mat.toMatchResult().group();
					EncryptToken = EncryptToken.substring(1, 33);
				}
			}
			SLog.d("EncryptToken = " + EncryptToken);
			this.strEncryToken = EncryptToken;
			
			StringBuilder sb = new StringBuilder(url);
			sb.append("?");
			sb.append("UserID=").append(this.strUserID);
			sb.append("&UserPwd=").append(this.strPassword);
			sb.append("&Lang=1");			// 1为本地语言
			sb.append("&SupportHD=1"); 		// 支持高清
			sb.append("&NetUserID=");		// 网络帐号
			sb.append("&Authenticator=").append(CTCGetAuthInfo(EncryptToken, EncryptionType));
			sb.append("&STBType=EC6106V6");		// STBType
			sb.append("&STBVersion=1.2.3.4");	// 版本号
			sb.append("&conntype=2");			// 接入方式
			sb.append("&STBID=").append(this.strAndroidID);
			sb.append("&templateName=default");
			sb.append("&areaId=");
			sb.append("&userToken=").append(EncryptToken);
			sb.append("&userGroupId=");
			sb.append("&productPackageId=");
			sb.append("&mac=").append(this.strMacAddr);
			sb.append("&UserField=");
			sb.append("&SoftwareVersion=1.2.3.4");
			sb.append("&isfirst=0");
			
			isHttpGet = false;
			url = sb.toString();
			SLog.d("url = " + url); 
			return url;
		} else if (currentUrl.indexOf("ValidAuthenticationHWCTC.jsp") > 0) {
			// 这个页面中有一个isSucessed变量来标识认证成功失败。
			// 失败的情况下下发的UserToken为null
			
			// 取认证结果
			Pattern	pattern = Pattern.compile("var\\s{1,}isSucessed\\s*=\\s*.*;");
			Matcher mat = pattern.matcher(content);
			boolean authSucess = false;
			if (mat.find()) {
				String sr = mat.toMatchResult().group();
				pos = sr.indexOf("=");
				if (pos > 0) {
					sr = sr.substring(pos + 1);
				}
				sr = sr.trim();
				if (sr.startsWith("true"))
					authSucess = true;
				else
					authSucess = false;
				SLog.d("Find auth result: " + authSucess);
			} else {
				SLog.d("Not Find auth result. ");
			}
			if (!authSucess) {
				m_status = LOGINMGR_AUTHFAIL;
				return null;
			}
			
			// 查找UserToken
			this.strUserToken = ParseCTCSetConfig(content, "UserToken");
			if  (strUserToken == null) {
				m_status = LOGINMGR_AUTHFAIL;
			} else {
				m_status = LOGINMGR_SUCCESS;
			}
    	} else {
			SLog.d("unknown page.");
		}
		// SLog.d("pos = " + pos);
		
    	return null;
    }
    
   
    private void NotifyListener(int status, int extra)
    {
    	if (m_listener == null) {
    		SLog.d("No listener found.");
    	}
    	final String[] table = new String[5];
    	table[LOGINMGR_SUCCESS] = "Success";
    	table[LOGINMGR_AUTHFAIL] = "Auth fail.";
    	table[LOGINMGR_TIMEDOUT] = "Timed out.";
    	table[LOGINMGR_HTTPFAIL] = "Http fail." + extra;
    	table[LOGINMGR_PARSEFAIL] = "Parse fail.";
    	m_listener.onLoginReturn("" + status, table[status]);
    }
    
    // 获取登录业务对象
    // 功能描述：   获取登录业务对象。
    // 参数说明：
    // 返回值：     返回登录业务对象实例
    public static SDKLoginMgr getInstance()
    {   
        if (m_instance == null) {
            m_instance = new SDKLoginMgr();
        }
        return m_instance;
    }

    // 设置登录事件的响应回调
    // 功能描述：   设置登录响应回调方法，登录相关的响应消息事件通知通过调用 ILoginReturnListener 中的方法进行通知。
    //              在调用登录方法前必须调用此方法。
    //              接口 ILoginReturnListener 的定义参见“回调接口”中“登录响应回调”、“注销响应回调”、“保活响应回调”小节。
    // 参数说明：   
    //              a) listener： 回调接口
    // 返回值：     无
    // 使用说明：   支持Listener对象的覆盖，如果多次调用此接口，只有最后设置的Lisenter对象能收到回调响应。
    public void setLoginReturnListener(SDKLoginMgr.ILoginReturnListener listener)
    {
        m_listener = listener;
    }

    // 初始化登录相关参数
    // 功能描述：   初始化系统参数
    // 参数说明：   登录相关参数：
    //              a) strClientType： 终端类型，1-STB 2-手机 3-PC 4-PAD
    //              b) strIpAddr： 本机IP地址
    //              c) strMacAddr： 本机mac地址
    //              d) strAndroidID： 本机设备唯一标识AndroidId
    // 返回值：     0：成功； 其他：失败
    public int initLogin(String strClientType, String strIpAddr, String strMacAddr, String strAndroidID)
    {
    	this.strClientType = strClientType;
    	this.strIpAddr = strIpAddr;
    	this.strMacAddr = strMacAddr;
    	this.strAndroidID = strAndroidID;
        return 0;
    }
    
    // 发起登录请求
    // 功能描述：   发起系统登录请求。此函数为异步方式返回，登录操作的响应消息在ILoginReturnListener中
    //              的onLoginReturn的方法中处理。当登录成功以后，会自动发起和系统之间的心跳保活，保活
    //              操作的结果通过回调函数（onHeartbeatReturn）返回。
    // 参数说明：   b) strUserID： 用户登录账户名
    //              c) strPassword： 用户密码
    // 返回值：     0：成功； 其他：失败
	public int startLogin(String strUserID, String strPassword)
    {
		this.strUserID = strUserID;
		this.strPassword = strPassword;
		isHttpGet = true;
		
		
		String url = ConcatenateUrl(true);
		SLog.d("builder = " + url);
		
    	DefaultHttpClient httpclient = new DefaultHttpClient();
		HttpGet httpget = new HttpGet(url);
		HttpPost httppost = new HttpPost(url);
		
		HttpParams httpParams = httpget.getParams();
		httpParams.setIntParameter("http.socket.timeout", 5000);
		httpParams.setIntParameter("http.connection.timeout", 9000);
		httpParams.setBooleanParameter("http.protocol.handle-redirects", false);
		httpget.setParams(httpParams);
		
		HttpResponse response;
		int code = 0;
		try {
			while (true) {
				SLog.d("request: " + url);
				if (isHttpGet) {
					// httpget.reset();
					httpget.setURI(URI.create(url));
					response = httpclient.execute(httpget);
				} else {
					int pos = url.indexOf("?");
					String ent = null;
					if (pos > 0) {
						ent = url.substring(pos + 1);
						url = url.substring(0, pos);
					}
					SLog.d("url = " + url);
					SLog.d("ent = " + ent);
					
					// httppost.reset();
					httppost.setURI(URI.create(url));
					
					if (ent != null) {
						HttpEntity entity = new StringEntity(ent);
						httppost.setEntity(entity);
					}
					httppost.setHeader("Content-Type", "application/x-www-form-urlencoded");
					response = httpclient.execute(httppost);
				}
				/*
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				*/
				
				code = response.getStatusLine().getStatusCode();

				// SLog.d("line = " + response.getStatusLine().toString());
				SLog.d("code = " + code);
				if (code == HttpStatus.SC_MOVED_TEMPORARILY 
						|| code == HttpStatus.SC_MOVED_PERMANENTLY
						|| code == HttpStatus.SC_SEE_OTHER
						|| code == HttpStatus.SC_TEMPORARY_REDIRECT) {
					Header header = response.getFirstHeader("Location");
					if (header == null) {
						m_status = LOGINMGR_HTTPFAIL;
				        // httpclient.close();
						return code;
					}
					String location = header.getValue();
					SLog.d("redirection url: " + location);
					location = CheckAndConcatenateNonAbsoluteUrl(url, location);
					SLog.d("redirection confirm: " + location);
					url = location;
					continue;
				} else if (code == HttpStatus.SC_NOT_FOUND) {
				} else if (code == HttpStatus.SC_OK) {
					String location = ParseAndGetNextUrl(response, url);
					if (location == null || location.isEmpty()) {
						break;
					} else {
						url = location;
						continue;
					} 
				} else {
					SLog.d("Unknown code: " + code);
					m_status = LOGINMGR_HTTPFAIL;
					// httpclient.close();
				}
				break;
			}

			// httpclient.close();

			
		} catch (ClientProtocolException e) {
			// TODO Auto-generated catch block
			SLog.d("error");
			e.printStackTrace();
		} catch (org.apache.http.conn.ConnectTimeoutException e) {
			e.printStackTrace();
			m_status = LOGINMGR_TIMEDOUT;
			return 0;
		} catch (IOException e) {
			// TODO Auto-generated catch block
			SLog.d("error1");
			e.printStackTrace();
		} finally {
		}
    	
		NotifyListener(m_status, code);
        return m_status;
    }

    // 发起注销请求
    // 功能描述：   发起注销请求。此方法为异步方式返回，注销操作的响应消息在ILogoutReturnListener中
    //              的onLogoutReturn方法进行处理。要处理注销操作的响应消息，必须首先确保已经调用setLoginReturnListener
    //              方法设置好登录事件响应回调接口。
    // 参数说明：   无
    // 返回值：     0：成功；其他：失败
    public int startLogout()
    {
		String url = ConcatenateUrl(true);
		SLog.d("builder = " + url);
		
    	DefaultHttpClient httpclient = new DefaultHttpClient();
		HttpGet httpget = new HttpGet(url);
		HttpPost httppost = new HttpPost(url);
		
		HttpParams httpParams = httpget.getParams();
		httpParams.setIntParameter("http.socket.timeout", 5000);
		httpParams.setIntParameter("http.connection.timeout", 9000);
		httpParams.setBooleanParameter("http.protocol.handle-redirects", false);
		httpget.setParams(httpParams);

		int code = 0;
		try {
			HttpResponse response = httpclient.execute(httpget);
			code = response.getStatusLine().getStatusCode();
			// httpclient.close();
		} catch (ClientProtocolException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		m_status = 0;
		NotifyListener(m_status, code);
        return 0;
    }
    
    // 获取用户信息
    // 功能描述：   登录后获取用户的相关信息，传入要查询的键值，返回指定键值对应的数据值。
    // 参数说明：   a) key：键 
    //              b) 支持获取以下键值：
    //              +------------+-----------------------------+
    //              |    键      |   说明                      |
    //              +------------+-----------------------------+
    //              | EncryToken | 加密Token                   |
    //              +------------+-----------------------------+
    //              | UserToken  | UserToken 用户唯一身份令牌  |
    //              +------------+-----------------------------+
    public String getUserInfo(String key)
    {
    	if (key.equals("EncryToken")) {
    		return this.strEncryToken;
    	} else if (key.equals("UserToken")) {
    		return this.strUserToken;
    	} else {
    		return null;
    	}
    }

}
